#![allow(dead_code, unused)]

use std::{
    ffi::*, ptr::{null, null_mut, copy, copy_nonoverlapping}, sync::{Arc, Mutex}
};
use ash::vk::{self, Handle};
use cgmath::{Matrix4, One};
use getset::{CloneGetters, CopyGetters, Getters, MutGetters, Setters, WithSetters};
use crate::engine::{njm_device::NjmDevice, njm_frame_info::FrameInfo, njm_pipeline::*, njm_utils::struct_to_bytes};

#[repr(C)]
pub struct SimplePushConstantData {
    model_mat: cgmath::Matrix4<f32>,
    normal_mat: cgmath::Matrix4<f32>,
}

impl Default for SimplePushConstantData {
    fn default() -> Self {
        return Self {
            model_mat: Matrix4::one(),
            normal_mat: Matrix4::one(),
        };
    }
}

#[derive(Getters, Setters, WithSetters, MutGetters, CopyGetters, CloneGetters)]
pub struct SimpleRenderSystem {
    dev: Arc<Mutex<NjmDevice>>,

    #[getset(get = "pub")]
    pipeline_layout: vk::PipelineLayout,
    #[getset(get = "pub")]
    njm_pipeline: Box<NjmPipeline>,

    #[getset(get = "pub")]
    vert_path: String,
    #[getset(get = "pub")]
    frag_path: String,
}

impl SimpleRenderSystem {

    pub fn new(
        dev: Arc<Mutex<NjmDevice>>,
        render_pass: vk::RenderPass,
        desc_set_layout: vk::DescriptorSetLayout,
        vpath: String, fpath: String
    ) -> Self {
        let njm_dev = dev.lock().unwrap();
        // layout
        let pc_range: vk::PushConstantRange = 
            vk::PushConstantRange::default()
            .stage_flags(vk::ShaderStageFlags::VERTEX | vk::ShaderStageFlags::FRAGMENT)
            .offset(0)
            .size(size_of::<SimplePushConstantData>() as u32)
            ;
        let pc_ranges = std::slice::from_ref(&pc_range);
        let desc_set_layouts = std::slice::from_ref(&desc_set_layout);

        let pipeline_layout_info = 
            vk::PipelineLayoutCreateInfo::default()
            .set_layouts(desc_set_layouts)
            .push_constant_ranges(pc_ranges);
        let pipeline_layout = unsafe { njm_dev.device().create_pipeline_layout(&pipeline_layout_info, None).unwrap() };

        // pipeline
        assert!(!pipeline_layout.is_null(), "SimpleRenderSystem::new : pipelineLayout is null");
        let mut pipeline_config = PipelineConfigInfo::default();
        NjmPipeline::default_pipeline_config(&mut pipeline_config);
        pipeline_config.render_pass = render_pass;
        pipeline_config.pipeline_layout = pipeline_layout;
        let njm_pipeline = Box::new(
            NjmPipeline::new(dev.clone(), &vpath, &fpath, &pipeline_config)
        );

        return Self {
            dev: dev.clone(),
            pipeline_layout: pipeline_layout,
            njm_pipeline: njm_pipeline,
            vert_path: vpath,
            frag_path: fpath,
        };
    }

    pub fn render(&self, frame_info: &FrameInfo) {
        self.njm_pipeline.bind(frame_info.cmd_buf);

        let njm_dev = self.dev.lock().unwrap();
        let desc_sets = std::slice::from_ref(&frame_info.desc_set);
        unsafe { njm_dev.device().cmd_bind_descriptor_sets(
            frame_info.cmd_buf, 
            vk::PipelineBindPoint::GRAPHICS, 
            self.pipeline_layout, 0, 
            desc_sets, &[]) };
            
        for (obj_id, obj) in frame_info.objs {
            if let Some(model) = &obj.model {
                let mut pc = SimplePushConstantData::default();
                pc.model_mat = obj.transform.mat4();
                pc.normal_mat = obj.transform.normal_mat();
                let pc_raw_slice: &[u8] = struct_to_bytes(&pc);
                
                unsafe { njm_dev.device().cmd_push_constants(
                    frame_info.cmd_buf, self.pipeline_layout, 
                    vk::ShaderStageFlags::VERTEX | vk::ShaderStageFlags::FRAGMENT, 
                    0, pc_raw_slice) };
                model.bind(frame_info.cmd_buf);
                model.draw(frame_info.cmd_buf);
            }
        }
    }

}

impl Drop for SimpleRenderSystem {
    fn drop(&mut self) {
        let njm_dev = self.dev.lock().unwrap();
        unsafe { njm_dev.device().destroy_pipeline_layout(self.pipeline_layout, None) };
    }
}
